# -*- coding: utf-8 -*-
import pandas as pd
import mplfinance as mpf
import matplotlib.pyplot as plt
from binance.client import Client
from datetime import datetime, timedelta
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk
from tdx import Zhibiao


class MplFinanceTkinterApp:
    def __init__(self, root):
        self.root = root
        self.root.title("BTC/USDT K线图分析")
        self.root.geometry("1200x800")

        # 初始化控制变量
        self.symbol_var = tk.StringVar(value="BTCUSDT")
        self.interval_var = tk.StringVar(value="5m")
        self.limit_var = tk.StringVar(value="200")

        # 设置全局中文字体
        self.style = mpf.make_mpf_style(
            base_mpf_style='charles',
            rc={'font.family': 'SimHei', 'axes.unicode_minus': False}
        )

        # 创建主框架（居中）
        self.main_frame = ttk.Frame(root)
        self.main_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)

        # 初始化图表相关变量
        self.fig = None
        self.axes = None
        self.canvas = None
        self.hover = None
        self.hover_cid = None

        # 添加控制按钮
        self.add_controls()

        # 获取数据并创建图表
        self.refresh_data()

    def get_binance_data(self, symbol='BTCUSDT', interval='5m'):
        """获取期货K线数据"""
        proxies = {
            'http': 'socks5://127.0.0.1:10010',
            'https': 'socks5://127.0.0.1:10010',
        }
        client = Client(requests_params={'proxies': proxies, 'timeout': 10})

        # 使用当前设置的时间周期和数量
        klines = client.futures_klines(
            symbol=symbol,
            interval=interval,  # 使用传入的interval参数
            limit=int(self.limit_var.get())
        )

        df = pd.DataFrame(klines, columns=[
            'Open time', 'open', 'high', 'low', 'close', 'volume',
            'Close time', 'Quote asset volume', 'Number of trades',
            'Taker buy base asset volume', 'Taker buy quote asset volume', 'Ignore'
        ])

        df['Open time'] = pd.to_datetime(df['Open time'], unit='ms')
        df.set_index('Open time', inplace=True)
        return df[['open', 'high', 'low', 'close', 'volume']].astype(float)

    def calculate_bollinger_bands(self, window=20, num_std=2):
        """计算布林线指标"""
        rolling_mean = self.data['close'].rolling(window=window).mean()
        rolling_std = self.data['close'].rolling(window=window).std()

        self.data['boll_upper'] = rolling_mean + (rolling_std * num_std)
        self.data['boll_middle'] = rolling_mean
        self.data['boll_lower'] = rolling_mean - (rolling_std * num_std)

    def create_chart(self):
        # 如果已有图表，先清除旧图表
        if hasattr(self, 'canvas') and self.canvas:
            self.canvas.get_tk_widget().destroy()

        # 计算布林线
        self.calculate_bollinger_bands()

        # 创建副图指标
        apds = [
            mpf.make_addplot(self.data['boll_upper'], color='blue', alpha=0.5),
            mpf.make_addplot(self.data['boll_middle'], color='orange', alpha=0.5),
            mpf.make_addplot(self.data['boll_lower'], color='blue', alpha=0.5),

            mpf.make_addplot(
                self.fxxs,
                panel=2,
                color="red",
                ylabel="指标",
                linestyle="-",
                alpha=0.8,
                y_on_right=False,
                ylim=(0, 100)
            ),
            mpf.make_addplot([80] * len(self.fxxs), panel=2, color="red", linestyle="-", y_on_right=False,ylim=(0, 100)),
            mpf.make_addplot([20] * len(self.fxxs), panel=2, color="green", linestyle="-", y_on_right=False,ylim=(0, 100))
        ]

        # 创建图表（使用当前设置的时间周期作为标题）
        self.fig, self.axes = mpf.plot(
            self.data,
            addplot=apds,
            type='candle',
            title=f'{self.symbol_var.get()} {self.interval_var.get()} K线图 | 数量: {self.limit_var.get()}',
            ylabel='价格（美元）',
            volume=True,
            style=self.style,
            returnfig=True,
            figsize=(11, 7),
            panel_ratios=(4, 1, 1),
            tight_layout=True,
            scale_padding={'left': 0.1, 'right': 0.2, 'top': 0.3, 'bottom': 0.8},
            update_width_config={'candle_linewidth': 0.5, 'candle_width': 0.8}
        )

        # 手动调整Y轴范围
        self.adjust_y_axis()

        # 同步X轴范围
        self.sync_axes()

        # 创建Tkinter画布
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.main_frame)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        # 添加鼠标交互
        self.connect_hover_event()

    def adjust_y_axis(self):
        """手动调整Y轴范围"""
        if len(self.axes) > 0:
            y_min = self.data['low'].min()
            y_max = self.data['high'].max()
            margin = (y_max - y_min) * 0.05
            self.axes[0].set_ylim(y_min - margin, y_max + margin * 1.5)

        if len(self.axes) > 1:
            vol_max = self.data['volume'].max()
            self.axes[1].set_ylim(0, vol_max * 1.2)

    def sync_axes(self):
        """同步所有子图的X轴范围"""
        if len(self.axes) > 1:
            xlim = self.axes[0].get_xlim()
            for ax in self.axes[1:]:
                if ax:
                    ax.set_xlim(xlim)
                    ax.autoscale_view(scalex=False)

    def connect_hover_event(self):
        if hasattr(self, 'hover_cid') and self.hover_cid:
            self.canvas.mpl_disconnect(self.hover_cid)

        self.hover = CandleHover(self.fig, self.axes, self.data, self.fxxs)
        self.hover_cid = self.canvas.mpl_connect('motion_notify_event', self.hover)

    def add_controls(self):
        control_frame = ttk.Frame(self.main_frame)
        control_frame.pack(fill=tk.X, padx=5, pady=5)

        # 刷新按钮
        refresh_btn = ttk.Button(control_frame, text="刷新数据", command=self.refresh_data)
        refresh_btn.pack(side=tk.LEFT, padx=5)

        # 时间周期选择
        interval_menu = ttk.OptionMenu(
            control_frame, self.interval_var, "5m",
            "1m", "5m", "15m", "30m", "1h", "4h", "1d",
            command=self.change_interval
        )
        interval_menu.pack(side=tk.LEFT, padx=5)
        ttk.Label(control_frame, text="周期:").pack(side=tk.LEFT, padx=(10, 0))

        # 品种选择
        symbol_menu = ttk.OptionMenu(
            control_frame, self.symbol_var, "BTCUSDT",
            "BTCUSDT", "ETHUSDT", "BNBUSDT", "ADAUSDT",
            command=self.change_symbol
        )
        symbol_menu.pack(side=tk.LEFT, padx=5)
        ttk.Label(control_frame, text="品种:").pack(side=tk.LEFT, padx=(10, 0))

        # K线数量选择
        limit_menu = ttk.OptionMenu(
            control_frame, self.limit_var, "200",
            "100", "200", "300", "400", "500", "600",
            command=self.change_limit
        )
        limit_menu.pack(side=tk.LEFT, padx=5)
        ttk.Label(control_frame, text="K线数量:").pack(side=tk.LEFT, padx=(10, 0))

    def refresh_data(self):
        """完全重新获取数据并刷新图表"""
        # 获取新数据（使用当前设置的参数）
        self.data = self.get_binance_data(
            symbol=self.symbol_var.get(),
            interval=self.interval_var.get()  # 使用当前设置的时间周期
        )

        # 重新计算指标
        self.fxxs, self.buy = Zhibiao.ths_chaodi(self.data)

        # 重新创建图表
        self.create_chart()

    def change_interval(self, event=None):
        """时间周期变更处理"""
        self.refresh_data()  # 完全重新加载数据

    def change_symbol(self, event=None):
        """品种变更处理"""
        self.refresh_data()  # 完全重新加载数据

    def change_limit(self, event=None):
        """K线数量变更处理"""
        self.refresh_data()  # 完全重新加载数据


class CandleHover:
    def __init__(self, fig, axes, data, fxxs):
        self.fig = fig
        self.axes = axes
        self.data = data
        self.fxxs = fxxs
        self.annotation = None
        self.vlines = []
        self.last_close = data['close'].iloc[-1]

    def __call__(self, event):
        if not event.inaxes or event.xdata is None:
            self._cleanup()
            return

        try:
            idx = int(round(event.xdata))
            if 0 <= idx < len(self.data):
                candle = self.data.iloc[idx]
                self._update_annotations(idx, candle, event)
            else:
                self._cleanup()
        except:
            self._cleanup()

        self.fig.canvas.draw_idle()

    def _update_annotations(self, idx, candle, event):
        # 计算振幅（最高价-最低价）/开盘价
        amplitude = ((candle['high'] - candle['low']) / candle['open']) * 100

        # 计算当日涨跌幅
        current_change = (candle['close'] - candle['open']) / candle['open'] * 100

        # 计算相对于最新K线的涨跌幅
        last_change = (self.last_close - candle['close']) / candle['close'] * 100

        self._cleanup()

        # 在所有面板上添加竖线
        for ax in self.axes:
            if ax:
                vline = ax.axvline(x=idx, color='dodgerblue', alpha=0.7, linestyle='--')
                self.vlines.append(vline)

        # 构建提示文本
        text = (f"日期: {self.data.index[idx].strftime('%Y-%m-%d %H:%M')}\n"
                f"收盘: {candle['close']:.2f}\n"
                f"振幅: {amplitude:.2f}%\n"
                f"涨跌幅: {current_change:+.2f}%\n"
                f"相对最新价: {last_change:+.2f}%\n"
                f"指标: {self.fxxs[idx]:.2f}")

        # 智能调整提示框位置
        x_offset, y_offset = self._smart_position(idx, candle)

        self.annotation = self.axes[0].annotate(
            text,
            xy=(idx, candle['high']),
            xytext=(x_offset, y_offset),
            textcoords='offset points',
            bbox=dict(boxstyle='round', fc='w', ec='0.5', alpha=0.9),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3')
        )

    def _smart_position(self, idx, candle):
        """智能计算提示框最佳显示位置"""
        is_right = idx > len(self.data) * 0.8
        y_range = self.axes[0].get_ylim()
        price_pos = (candle['high'] - y_range[0]) / (y_range[1] - y_range[0])
        is_top = price_pos > 0.9

        if is_right and is_top:
            return -120, -80
        elif is_right:
            return -120, 10
        elif is_top:
            return 15, -80
        else:
            return 15, 10

    def _cleanup(self):
        if self.annotation:
            self.annotation.remove()
            self.annotation = None
        for vline in self.vlines:
            if vline:
                vline.remove()
        self.vlines = []


if __name__ == "__main__":
    root = tk.Tk()
    app = MplFinanceTkinterApp(root)
    root.mainloop()